<?php
namespace App\Services\Rbac;
use App\Repositories\RbacGroupRepository;
use App\Repositories\RbacRuleRepository;
use App\Repositories\RbacUserRepository;
use App\Services\Helper;

/**
 * 权限菜单相关操作
 *
 * @author: yanxs <541139655@qq.com>
 * @version: 1.0
 * @date: 2020/12/3
 */
class Menu
{
    protected $repoUser;  //管理员仓库
    protected $repoAuthGroup; //角色仓库
    protected $repoAuthRule; //菜单仓库
    protected $groupId;    //角色id
    protected $extWhere;  //附加条件
    protected $defaultPage; //默认显示页面
    protected $ruleList;    //角色对应的权限节点
    protected $menuTpl;     //菜单视图模板
    /**
     * 可以注入不同的User,group,rule不同表的rbac体系
     * Menu constructor.
     * @param RbacUserRepository $user
     * @param RbacGroupRepository $group
     * @param RbacRuleRepository $rule
     */
    public function __construct(RbacUserRepository $user = NULL, RbacGroupRepository $group  = NULL, RbacRuleRepository $rule  = NULL)
    {
        $this->repoUser = $user;
        $this->repoAuthGroup = $group;
        $this->repoAuthRule = $rule;
    }

    /**
     * @param int $groupId 角色组id
     * @param array $where 附加条件，用于做头部导航用
     * @param string $fixedPage  固定页路由
     * @return string
     */
    public function getSidebar(int $groupId, $where = [], $fixedPage = 'dashboard')
    {
        $this->extWhere = $where;
        $this->defaultPage = $fixedPage;

        //获取节点列表
        $list = $this->getRuleList($groupId);
        $selectId = 0;
        foreach ($list as $k => &$v) {
            $selectId = $v['rule_route'] == $fixedPage || $v['rule_route'] == '/'.$fixedPage ? $v['id'] : $selectId;
            $v['menu_url'] = '/'.$v['rule_route'];
        }
        $this->ruleList = $list;
        //获取菜单树的视图数据
        $this->setMenuTpl();
        $str = $this->getTreeMenu(0,$this->menuTpl, $selectId, '', 'ul', 'class="nav nav-treeview menu-lv-1"', 0);

        return $str;
    }

    /**
     * 获取菜单模板视较长
     * @param string $strTpl
     * @return string
     */
    public function setMenuTpl($strTpl = '')
    {
        if (empty($strTpl)) {
            $strTpl =  '<li class="@class"><a href="javascript:void(0);" data-id="@id" data-url="@url" class="@childClass"><i class="nav-icon @rule_icon"></i> <p>@rule_name@caret</p></a> @childlist</li>';
        }
        $this->menuTpl = $strTpl;
    }

    /**
     * 获取菜单编辑树视图
     * @param int $id
     * @return string
     */
    public function getRuleListView($id=0)
    {
        $list = $this->repoAuthRule->getRows(['status'=>1], 'sort', 'asc')->toArray();

        $upNode = []; //记录存在子节点的数组
        //组织树组件需要的数据
        foreach ($list as $k=>$v){
            $ruleList[] = [
                'id'=>$v['id'],
                'parent'=>$v['rule_pid'] ? $v['rule_pid'] : '#',
                'text'=>$v['rule_name'],
                'state'=>["selected"=>false],
            ];


            $upNode[] = $v['rule_pid'];

        }

        $ruleParent = array_column($ruleList,'parent');

        if(!empty($id)){ //编辑
            $groupRule = $this->repoAuthGroup->getById($id);
            $groupRule = explode(',',$groupRule['group_rule']);

            foreach ($ruleList as $rk=>$rv){
                if((in_array($rv['id'],$groupRule))){
                    $ruleList[$rk]['state'] = ["selected"=>true];
                }
                //所有有下级的节点状态全为false
                if (in_array($rv['id'],$upNode) || $rv['parent'] == "#") {
                    $ruleList[$rk]['state'] = ["selected"=>false];
                }
                if (!in_array($rv['id'],$upNode) && $rv['parent'] == "#" && in_array($rv['id'],$groupRule)) {
                    $ruleList[$rk]['state'] = ["selected"=>true];
                }

                if (in_array("*", $groupRule)) {
                    $ruleList[$rk]['state'] = ["selected"=>true];
                }
            }
        }
        return json_encode($ruleList,JSON_UNESCAPED_UNICODE);

    }

    /**
     * 获取菜单树视图字符串
     * @param $pid 节点父id
     * @param $itemTpl 模板视图
     * @param string $selectId 所选中的id
     * @param string $disableId 禁止的id
     * @param string $wrapTag  外部标签
     * @param string $wrapAttr 标签属性
     * @param int $deepLevel 节点层级
     * @return string
     */
    protected function getTreeMenu($pid, $itemTpl, $selectId='', $disableId='', $wrapTag='ul', $wrapAttr ='', $deepLevel=0)
    {
        $str = '';
        $childs = $this->getChild($pid);
        if ($childs) {
            foreach ($childs as $ck => $value) {
                $id = $value['id'];
                unset($value['child']);
                $selected = in_array($id, (is_array($selectId) ? $selectId : explode(',', $selectId))) ? 'selected' : '';
                $disabled = in_array($id, (is_array($disableId) ? $disableId : explode(',', $disableId))) ? 'disabled' : '';
                $value = array_merge($value, array('selected' => $selected, 'disabled' => $disabled));
                $value = array_combine(array_map(function($k) {
                    return '@' . $k;
                }, array_keys($value)), $value);

                $bakValue = array_intersect_key($value, array_flip(['@url', '@caret', '@class']));
                $value = array_diff_key($value, $bakValue);
                $nstr = strtr($itemTpl, $value);
                $value = array_merge($value, $bakValue);
                $childData = $this->getTreeMenu($id, $itemTpl, $selectId, $disableId, $wrapTag, $wrapAttr, $deepLevel + 1);
                $childList = $childData ? "<{$wrapTag} {$wrapAttr}>" . $childData . "</{$wrapTag}>" : "";
                $childList = strtr($childList, array('@class' => $childData ? 'last' : ''));
                $value = array(
                    '@childlist' => $childList,
                    '@url'       => $childData || !isset($value['@rule_route']) ? " " : $value['@rule_route'],
                    '@addtabs'   => $childData || !isset($value['@rule_route']) ? "" : (stripos($value['@rule_route'], "?") !== false ? "&" : "?") . "",
                    '@caret'     => ($childData && (!isset($value['@badge']) || !$value['@badge']) ? '<i class="fa fa-angle-left right fa-lg"></i>' : ''),
                    '@badge'     => isset($value['@badge']) ? $value['@badge'] : '',
                    '@class'     => ($selected ? 'nav-item has-treeview menu-open' : 'nav-item has-treeview') . ($disabled ? ' disabled' : '') . ($childData ? '' : ''),
                    '@childClass'=> $deepLevel == 1 ? 'nav-link menu-link' : ($selected ? 'nav-link menu-link active menu-default' : 'nav-link')
                );
                $str .= strtr($nstr, $value);
            }

        }
        return $str;
    }


    /**
     * 得到树形结数组
     * @param $pid
     * @return array
     */
    protected function getChild($pid)
    {
        $tree = [];
        foreach ($this->ruleList as $value)
        {
            if (!isset($value['id']))
                continue;
            if ($value['rule_pid'] == $pid)
                $tree[$value['id']] = $value;
        }
        return $tree;
    }

    /**
     * 获取权限节点列表
     * @param int $groupId
     * @return array
     */
    public function getRuleList($groupId = 0, $is_menu = 1)
    {
        //获取角色的权限节点
        $ruleIds = $this->getRuleIds($groupId);
        $this->groupId = $groupId;
        $ruleList = [];
        if (empty($ruleIds)) {
            $ruleList[$groupId] = [];
        }
        //组织获取结点条件
        if (!empty($is_menu)) {
            $where['is_menu'] = $is_menu;
        }

        $where['status']  = 1;
        //非超级管理员权限
        if (!in_array('*', $ruleIds)) {
            $where['id'] = $ruleIds;
        }
        //引入额外条件
        if (!empty($this->extWhere)) {
            //$where = $this->extWhere;
            foreach ($this->extWhere as $ek => $ev) {
                $where[$ek] = $ev;
            }
        }
        $ruleList = $this->repoAuthRule->getRows($where, 'sort')->toArray();

        $_rules = [];
        //获取rule_id对应的route
        foreach ($ruleList as $rule) {
            $rule['rule_route'] = strpos(strtolower($rule['rule_route']), '/') ===0 ? strtolower($rule['rule_route']): '/'.strtolower($rule['rule_route']);
            $_rules[$rule['id']] = $rule;
        }

        return $_rules;
    }
    /**
     * 通过角色组id获了权限节点
     * @param int $groupId 角色组id
     * @return array
     */
    protected function getRuleIds($groupId = 0)
    {
        //获取权限节点
        $info = $this->repoAuthGroup->getById($groupId);
        if (empty($info)) {
            header("Location:/login");
        }
        $ruleIds = explode(',', $info['group_rule']);
        //注意['*']表示有全部权限
        return $ruleIds ?? [];

    }

    /**
     * 检查路由权限
     * @param $url
     * @param $noNeedRight
     * @param $groupId
     * @return bool
     */
    public function mathUrl($url,$noNeedRight, $groupId = 0)
    {
        if(in_array("*",$noNeedRight) || $url == "/"){
            //无需检查权限
            return true;
        }
        $groupInfo = $this->repoAuthGroup->getById($groupId);
        if ($groupInfo['group_rule'] == "*") {
            return true;
        }

        // 读取管理员当前拥有的权限节点
        $userRule = $this->getRuleList($groupId, 0);
        //定义默认方法
        $defaultNoNeedRight = [];
        //检查是否有权限访问
        foreach ($userRule as $v){
            if(strpos($url,$v['rule_route']) !== false){
                $currentArr = explode('/',$url);
                $str = array_pop($currentArr);
                //检查是否请求默认方法
                foreach ($defaultNoNeedRight as $value){
                    if(strpos($url,$value)){
                        return true;
                    }
                }

                if(in_array($str,$noNeedRight)){
                    return true;
                }else if($url ==  $v['rule_route']){
                    return true;
                }
            }
        }
        return false;
    }
}
